#include <iostream>
#include <fstream>

#include <libavcodec/avcodec.h>
#include "mpegts_demuxer.h"

typedef enum ErrorCode {
    kErrorCode_Success = 0,
    kErrorCode_Invalid_Param,
    kErrorCode_Invalid_State,
    kErrorCode_Invalid_Data,
    kErrorCode_Invalid_Format,
    kErrorCode_NULL_Pointer,
    kErrorCode_Open_File_Error,
    kErrorCode_Eof,
    kErrorCode_FFmpeg_Error
}ErrorCode;

typedef enum LogLevel {
    kLogLevel_None, //Not logging.
    kLogLevel_Core, //Only logging core module(without ffmpeg).
    kLogLevel_All   //Logging all, with ffmpeg.
}LogLevel;

//AAC (ADTS) audio
#define TYPE_AUDIO 0x0f
//H.264 video
#define TYPE_VIDEO_H264 0x1b
//H.265 video
#define TYPE_VIDEO_H265 0x24
//MPGE-II video
#define TYPE_VIDEO_MPGE2 0x02

using namespace mpegts;

MpegTsDemuxer* gDemuxer;

uint8_t packet[188] = {0};
SimpleBuffer in;

static long count = 0;
int decoderType = 0;
int logLevel = kLogLevel_All;
extern "C" {
    extern void outYUVFrame(unsigned char* data_y, unsigned char* data_u, unsigned char* data_v, int line1, int line2, int line3, int width, int height, long pts);
    int isInit = 0;
    const AVCodec* codec;
    AVCodecParserContext* parser;
    AVCodecContext* c = NULL;
    AVPacket* pkt;
    AVFrame* frame;
    AVFrame* outFrame;
    long ptslist[10];

    void simpleLog(const char* format, ...) {
        if (logLevel == kLogLevel_None) {
            return;
        }
    
        char szBuffer[1024] = { 0 };
        char szTime[32] = { 0 };
        char* p = NULL;
        int prefixLength = 0;
        const char* tag = "Core";
    
        prefixLength = sprintf(szBuffer, "[%s][%s][DT] ", szTime, tag);
        p = szBuffer + prefixLength;
    
        if (1) {
            va_list ap;
            va_start(ap, format);
            vsnprintf(p, 1024 - prefixLength, format, ap);
            va_end(ap);
        }
    
        printf("%s\n", szBuffer);
    }
    void ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list vl) {
        static int printPrefix = 1;
        static int count = 0;
        static char prev[1024] = { 0 };
        char line[1024] = { 0 };
        static int is_atty;
        AVClass* avc = ptr ? *(AVClass**)ptr : NULL;
        if (level > AV_LOG_DEBUG) {
            return;
        }
    
        line[0] = 0;
    
        if (printPrefix && avc) {
            if (avc->parent_log_context_offset) {
                AVClass** parent = *(AVClass***)(((uint8_t*)ptr) + avc->parent_log_context_offset);
                if (parent && *parent) {
                    snprintf(line, sizeof(line), "[%s @ %p] ", (*parent)->item_name(parent), parent);
                }
            }
            snprintf(line + strlen(line), sizeof(line) - strlen(line), "[%s @ %p] ", avc->item_name(ptr), ptr);
        }
    
        vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl);
        line[strlen(line) + 1] = 0;
        simpleLog("%s", line);
    }
    
    __attribute__((used)) ErrorCode openDecoder(int codecType, int logLv) {
        ErrorCode ret = kErrorCode_Success;
        do {
            
            if (isInit != 0) {
                break;
            }
            decoderType = codecType;
            logLevel = logLv;

            
            av_log_set_callback(ffmpegLogCallback);
          

            /* find the video decoder */
            codec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);

            if (!codec) {
                simpleLog("Codec not found\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            parser = av_parser_init(codec->id);
            if (!parser) {
                simpleLog("parser not found\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            c = avcodec_alloc_context3(codec);
            if (!c) {
                simpleLog("Could not allocate video codec context\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            if (avcodec_open2(c, codec, NULL) < 0) {
                simpleLog("Could not open codec\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            frame = av_frame_alloc();
            if (!frame) {
                simpleLog("Could not allocate video frame\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            outFrame = av_frame_alloc();
            if (!outFrame) {
                simpleLog("Could not allocate video frame\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            pkt = av_packet_alloc();
            if (!pkt) {
                simpleLog("Could not allocate video packet\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }

            for (int i = 0; i < 10; i++) {
                ptslist[i] = LONG_MAX;
            }
        } while (0);
        simpleLog("Decoder initialized %d.", ret);
        return ret;
    }
    ErrorCode copyFrameData(AVFrame* src, AVFrame* dst, long ptslist[]) {
        ErrorCode ret = kErrorCode_Success;
        memcpy(dst->data, src->data, sizeof(src->data));
        dst->linesize[0] = src->linesize[0];
        dst->linesize[1] = src->linesize[1];
        dst->linesize[2] = src->linesize[2];
        dst->width = src->width;
        dst->height = src->height;
        long pts = LONG_MAX;
        int index = -1;
        for (int i = 0; i < 10; i++) {
            if (ptslist[i] < pts) {
                pts = ptslist[i];
                index = i;
            }
        }
        if (index > -1) {
            ptslist[index] = LONG_MAX;
        }
        dst->pts = pts;
        return ret;
    }
    static ErrorCode decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, AVFrame* outFrame, long ptslist[])
    {
        ErrorCode res = kErrorCode_Success;
        char buf[1024];
        int ret;

        ret = avcodec_send_packet(dec_ctx, pkt);
        if (ret < 0) {
            simpleLog("Error sending a packet for decoding\n");
            std::cout << ret << std::endl;
            res = kErrorCode_FFmpeg_Error;
        }
        else {
            std::cout << ret << std::endl;
            while (ret >= 0) {
                ret = avcodec_receive_frame(dec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                }
                else if (ret < 0) {
                    simpleLog("Error during decoding\n");
                    res = kErrorCode_FFmpeg_Error;
                    break;
                }

                res = copyFrameData(frame, outFrame, ptslist);
                if (res != kErrorCode_Success) {
                    break;
                }

                //videoCallback(outFrame->data[0], outFrame->data[1], outFrame->data[2], outFrame->linesize[0], outFrame->linesize[1], outFrame->linesize[2], outFrame->width, outFrame->height, outFrame->pts);
                outYUVFrame(outFrame->data[0], outFrame->data[1], outFrame->data[2], outFrame->linesize[0], outFrame->linesize[1], outFrame->linesize[2], outFrame->width, outFrame->height, outFrame->pts);
            }
        }
        return res;
    }
    __attribute__((used)) ErrorCode decodeData(unsigned char* data, size_t data_size, long pts) {
        ErrorCode ret = kErrorCode_Success;
    
        for (int i = 0; i < 10; i++) {
            if (ptslist[i] == LONG_MAX) {
                ptslist[i] = pts;
                break;
            }
        }
    
        while (data_size > 0) {
            int size = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
                data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
            if (size < 0) {
                simpleLog("Error while parsing\n");
                ret = kErrorCode_FFmpeg_Error;
                break;
            }
            data += size;
            data_size -= size;
    
            if (pkt->size) {
                ret = decode(c, frame, pkt, outFrame, ptslist);
                if (ret != kErrorCode_Success) {
                    break;
                }
                
            }
        }
        return ret;
    }
    
    __attribute__((used)) ErrorCode flushDecoder() {
        /* flush the decoder */
        return decode(c, frame, NULL, outFrame, ptslist);
    }
    
    __attribute__((used)) ErrorCode closeDecoder() {
        ErrorCode ret = kErrorCode_Success;
        do {
            if (parser != NULL) {
                av_parser_close(parser);
                simpleLog("Video codec context closed.");
            }
            if (c != NULL) {
                avcodec_free_context(&c);
                simpleLog("Video codec context closed.");
            }
            if (frame != NULL) {
                av_frame_free(&frame);
            }
            if (pkt != NULL) {
                av_packet_free(&pkt);
            }
            //if (yuvBuffer != NULL) {
            //    av_freep(&yuvBuffer);
            //}
            if (outFrame != NULL) {
                av_frame_free(&outFrame);
            }
            simpleLog("All buffer released.");
        } while (0);
        return ret;
    }
    //extern void outFrame(char *,int size);
    //extern void outYUV(uint8_t *,uint8_t *,uint8_t *,int width,int height);
    
    void setPacket(int index,unsigned char value){
        packet[index] = value;
    }

    void dmxOutput(const EsFrame &esFrame) {
        //std::cout << esFrame.mData->size() << std::endl;
        //std::cout << (short)esFrame.mStreamType << std::endl;
        if (esFrame.mStreamType == TYPE_VIDEO_H264) {
            //outFrame((char *)esFrame.mData->data(),esFrame.mData->size());
            //std::cout << esFrame.mData->size() << std::endl;
         /* u8** pic = nullptr;
            u32* width = nullptr;
            u32* height = nullptr;
            u32 flag = h264bsdDecode(pstorage_t,esFrame.mData->data(),esFrame.mData->size(),pic,width,height);
            if(flag == 1){
                unsigned char *inputImg = (unsigned char *)malloc(esFrame.mData->size());
                readSequence(inputImg,*width,*height);
                char name[] = {"F:/tmp.bmp"};
                saveToBmp(inputImg, *width, *height, name);
                free(inputImg);
            }*/
        }
        if (esFrame.mStreamType == TYPE_VIDEO_MPGE2) {
            //uint8_t * end = esFrame.mData->data() + esFrame.mData->size();
            //std::cout << count << std::endl;
            count++;
            std::cout << esFrame.mData->size() << std::endl;
            decodeData(esFrame.mData->data(),esFrame.mData->size(),count);
            flushDecoder();
            //closeDecoder();
            /*int num_frames = mpeg2_decode_data (&mpeg2dec, esFrame.mData->data(), end);
            std::cout << num_frames << std::endl;
            if(num_frames > 0){
                //char name[] = {"F:/tmp.bmp"};
                vo_frame_t * frame = mpeg2dec.picture->current_frame;
                //outYUV(frame->base[0],frame->base[1],frame->base[2],mpeg2dec.picture->coded_picture_width,mpeg2dec.picture->coded_picture_height);
                //mpeg2_drop(&mpeg2dec,1);
               //unsigned char * rgb_buf = (unsigned char *)malloc(640 * 384 * 3);
               //fn_Convert_Yuv(rgb_buf,frame->base[0],frame->base[1],frame->base[2],640, 384);
               //fn_Make_Bmp(rgb_buf,name,640, 384);
               //free(rgb_buf);
               count++;
            }
            if(count == 24){
                    //mpeg2_reset(&mpeg2dec,output);
                    count = 0;
                    std::cout << "mpeg2_reset" << std::endl;
            }
           */
        }
    }
    void init(){
        gDemuxer = new MpegTsDemuxer();
        gDemuxer->esOutCallback = std::bind(&dmxOutput, std::placeholders::_1);
        //return &packet[0];
    }
    void demuxer(){
        in.append(packet, 188);
        gDemuxer->decode(in);
        //std::cout << "demuxer!\n" << std::endl;
    }
    void destory(){
       
    }
}

int main(int, char**){
    openDecoder(1,kLogLevel_All);
    init();

    /*
    std::ifstream ifile("F:/example.ts", std::ios::binary | std::ios::in);
    while (!ifile.eof()) {
        ifile.read((char*)&packet[0], 188);
        demuxer();
    }
    ifile.close();
    mpeg2_close (&mpeg2dec);
    vo_close (output);
    */
    std::cout << "Hello, from 4 tsdemux!\n" << std::endl;

}





    

    
